#include <string>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/qi_symbols.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include "big_int.h"
#include "calculator.h"
#include "user_functions.h"

namespace 
{
   namespace functions
   {
      struct create_big_int_impl
      {
         template<typename T>
         struct result
         {
            typedef big_int type;
         };
         template<typename T>
         big_int operator()(T a) const
         {
            return big_int(a);
         }
      };

      struct call_user_function_impl
      {
         template<typename T, typename K>
         struct result
         {
            typedef big_int type;
         };
         template<typename T, typename K>
         big_int operator()(T a, K b) const
         {
            return get_functions_map()[a](b);
         }
      };

      boost::phoenix::function<call_user_function_impl> call_user_function()
      {
         static boost::phoenix::function<call_user_function_impl> f;
         return f;
      }

      boost::phoenix::function<create_big_int_impl> create_big_int()
      {
         static boost::phoenix::function<create_big_int_impl> f;
         return f;
      }
   }

   namespace qi = boost::spirit::qi;
   
   template <typename Iterator>
   struct calculator : qi::grammar<Iterator, big_int(), qi::ascii::space_type>
   {
      qi::rule<Iterator, big_int(), qi::ascii::space_type>
         expression, multiply, power, number, sum, start;
      qi::rule<Iterator, std::string(), qi::ascii::space_type>
         function_name, big_int_string;

      calculator() : calculator::base_type(start, "Calculator") 
      {
         using qi::_1;
         using qi::_2;
         using qi::_val;
         using qi::lit;
         function_name = +(qi::ascii::alpha);
         big_int_string = +(qi::ascii::digit);
         number = big_int_string[_val = functions::create_big_int()(_1)];
         expression = number[_val = _1] | (lit("-") >> expression)[_val = -_1] | (lit("(") >> sum >> lit(")"))[_val = _1] | (function_name >> lit("(") >> sum >> lit(")"))[_val = functions::call_user_function()(_1, _2)];
         power = expression[_val = _1] >> -(lit("^") >> power)[_val = _val ^ _1];
         multiply = power[_val = _1] >> * ((lit("*") >> power)[_val *= _1] | (lit("/") >> power)[_val /= _1]);
         sum = multiply[_val = _1] >> *((lit("+") >> multiply)[_val += _1] | (lit("-") >> multiply)[_val -= _1]);
         start %= sum > qi::eoi;
      }
   };
}

std::map<std::string, function_pointer_t>& get_functions_map()
{
   static std::map<std::string, function_pointer_t> functions_map;
   return functions_map;
}

calc::calc()
{
   get_functions_map()["sqr"] = function_pointer_t(sqr);
   get_functions_map()["digits"] = function_pointer_t(digits);
   get_functions_map()["fact"] = function_pointer_t(fact);
}

big_int calc::calculate(const std::string & expression)
{
   calculator<std::string::const_iterator> calculator_;
   big_int ans;
   boost::spirit::qi::phrase_parse(expression.begin(), expression.end(), calculator_, boost::spirit::qi::ascii::space, ans);
   return ans;
}